#region License Revision: 0 Last Revised: 6/18/2008 11:00 AM
/******************************************************************************
Copyright (c) Microsoft Corporation.  All rights reserved.


This file is licensed under the Microsoft Public License (Ms-PL). A copy of the Ms-PL should accompany this file. 
If it does not, you can obtain a copy from: 

http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
******************************************************************************/
#endregion // License
// VIEWS Sample
// Common Library for Silverlight Popup
// MS Research 2008

#region NOTES
/*******************************************************************************
 * This class is effectively the entrypoint for the sample. SLController is 
 * initialized in Page.cs by the Page_Loaded event handler. 
 * 
 * Once inside Initialize we create a new instance of Silverlight VEMap and 
 * pass the name of the HTML DIV into its constructor. The Silverlight 
 * VEMap constructor internally usese CreateClass to ask the browser to 
 * create an underlying JavaScript version of VEMap. The name of the HTML DIV 
 * is passed into the JavaScript constructor as well, so that the map is 
 * created where we expect it. 
 ******************************************************************************/
#endregion // NOTES
using System;
using System.Windows.Browser;
using System.Text;
using System.Collections.ObjectModel;
using VIEWS;
using SilverlightPopup;

namespace SilverlightMap
{
	/// <summary>
	/// Silverlight object exposed to the webpage
	/// </summary>
    public class SLController
    {
        #region Constants
        private const int iconMax = 16;

        // color rotation for route colors
        static private VEColor[] rgColor = {
				new VEColor(255, 0, 0, 1.0),
				new VEColor(0, 255, 0, 1.0),
				new VEColor(0, 0, 255, 1.0),
				new VEColor(255, 255, 0, 1.0),
				new VEColor(0, 255, 255, 1.0),
				new VEColor(255, 0, 255, 1.0),
				new VEColor(128, 0, 0, 1.0),
				new VEColor(0, 128, 0, 1.0),
				new VEColor(0, 0, 128, 1.0),
				new VEColor(128, 128, 0, 1.0),
				new VEColor(0, 128, 128, 1.0),
				new VEColor(128, 0, 128, 1.0)
			};

        //Generate the XAML we want for a popup
        private const string strPanel =
        @"<StackPanel Orientation=""Vertical"" MinWidth=""50""
				xmlns=""http://schemas.microsoft.com/client/2007"" 
				xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
				{0}
			</StackPanel>";

        // xaml for each line
        private const string strLine = @"<TextBlock Text=""{0}"" MinWidth=""50"" />";
        #endregion // Constants

        #region Member Variables
        private int currentColor = 0;
        private int currentIcon = 0;
        private VEMap map; // the Virtual Earth map
        private PaneInfo paneInfo; // pane variables
        private Page page; // the main pane
        private PopupData popupData; // data structure to pass data to popup
        #endregion // Member Variables

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="page">Silverlight page</param>
        public SLController(Page page)
        {
            this.page = page;

            // add the pane informatin structure
            paneInfo = new PaneInfo();

            // setup the listbox data binding
            this.page.lbFindResults.ItemsSource = paneInfo.lstFind;
            this.page.lbRoute.ItemsSource = paneInfo.lstRoute;

            popupData = new PopupData(0, 0, "");
        }
        #endregion // Constructors

        #region Helper Methods
        // private URI builder, so we can construct full URIs for the controls to use
        // full URIs are required, a relative path from the web site root do not work.
        private string baseUri;
        private string GetUri(string path)
        {
            if (baseUri == null)
            {
                baseUri = HtmlPage.Document.DocumentUri.AbsoluteUri;
                baseUri = baseUri.Substring(0, baseUri.LastIndexOf('/') + 1);
            }
            string str = baseUri + path;
            return str;
        }

        /// <summary>
        /// Xml Encode the strings so special characters come out correctly
        /// </summary>
        /// <param name="strXml">string to xml encode</param>
        /// <returns>encoded string</returns>
        static private string XmlEncode(string strXml)
        {
            string str = strXml.Replace("&", "&amp;");
            str = str.Replace("<", "&lt;");
            str = str.Replace(">", "&gt;");
            str = str.Replace(@"""", "&quot;");
            str = str.Replace(@"'", "&apos;");
            return str;
        }
        #endregion // Helper Methods

        #region Internal Methods
        /// <summary>
        /// Get the next color in our rotation of colors
        /// </summary>
        /// <param name="opacity">opacity of the color we want</param>
        /// <returns></returns>
        private ScriptObject GetNextColor(double opacity)
        {
            VEColor newColor = rgColor[currentColor++];
            if (currentColor >= rgColor.Length)
                currentColor = 0;
            newColor.A = opacity;
            return newColor;
        }

        /// <summary>
        /// Get the next icon in our rotation
        /// </summary>
        /// <returns>URI to the next icon</returns>
        private string GetNextIcon()
        {
            string strRelPath = string.Format("image/pin{0}.png", currentIcon.ToString("D2"));
            if (++currentIcon >= iconMax)
                currentIcon = 0;
            return GetUri(strRelPath);
        }
        #endregion // Internal Methods

        #region Overrides / Event Handlers
        private void map_Click(object sender, VEMapEventArgs e)
        {
            // get the shape object this refers to, if it's not a shape object, NULL will result
            VEShape shp = map.GetShapeById(e.ElementId);
            if (shp == null) { return; }

            // check to see if it's our type
            FoundPin foundPin = shp as FoundPin;
            if (foundPin == null) { return; }

            int cnt = foundPin.Lines.Count;

            // if checked pick up the address
            if ((bool)page.ToCheck.IsChecked)
            {
                page.tbTo.Text = foundPin.Lines[cnt - 1];
                page.ToCheck.IsChecked = false;
            }

            // if checked pick up the address
            if ((bool)page.FromCheck.IsChecked && cnt > 0)
            {
                page.tbFrom.Text = foundPin.Lines[cnt - 1];
                page.FromCheck.IsChecked = false;
                page.ToCheck.IsChecked = true;
            }

            // get the latitude and longitude
            int x, y;
            if (map.MapMode == VEMapMode.Mode3D)
            {
                VELatLong latlong = e.LatLong; // for 3D mode only
                VEPixel pix = map.LatLongToPixel(latlong);
                x = pix.X;
                y = pix.Y;
            }
            else
            {
                x = e.MapX;
                y = e.MapY;
            }

            StringBuilder sb = new StringBuilder();
            foreach (string line in foundPin.Lines)
            {
                sb.AppendFormat(strLine, XmlEncode(line));
            }

            string strXaml = string.Format(strPanel, sb.ToString());
            popupData.X = x;
            popupData.Y = y;
            popupData.Xaml = strXaml;
            popupData.Show();

            e.Handled = true;
        }

        private void map_Error(object sender, VEMapEventArgs e)
        {
            HtmlPage.Window.Alert(e.Error);
            e.Handled = true;
        }

        private void map_FindCompleted(object sender, FindCompletedEventArgs e)
        {
            // get the next icon color we want
            int cnt = paneInfo.lstFind.Count;
            FindLayer findLayer = null;
            VEShapeLayer layer = null;

            // wince we added the listbox item already, we can get the last one, and it should be the correct item.
            if (cnt > 0)
            {
                findLayer = paneInfo.lstFind[cnt - 1];
                layer = findLayer.Layer;
            }

            // if we let the find API add all the shapes for us, add them to the map
            if (e.Layer != null)
            {
                map.AddShapeLayer(e.Layer);
            }

            // if we got a list of find results, lets make our own shape layer with our icons
            if (e.Results != null && findLayer != null && layer != null)
            {
                foreach (VEFindResult fr in e.Results)
                {
                    FoundPin fp = new FoundPin(fr.LatLong, fr.Name, fr.Phone, fr.Description);
                    // create our custom icon that can be shown in 2D or 3D modes
                    VECustomIconSpecification cis = new VECustomIconSpecification();
                    cis.Image = findLayer.IconPath;
                    cis.CustomHtml = "<img src='" + findLayer.IconPath + "' />";
                    fp.CustomIconSpecification = cis;
                    // add shape to layer
                    layer.AddShape(fp);
                }
                // now add the layer to the map
                map.AddShapeLayer(layer);
            }

            // this is here if we want to process the possible places, if we use the disabiguation dialog (as we are), the first one is the one we want
            if (e.Places != null && findLayer != null && layer != null)
            {
                // if there was no "what" query, then add a single pin for the location 
                if (e.Results == null && e.Places.Length > 0)
                {
                    FoundPin fp = new FoundPin(e.Places[0].LatLong, e.Places[0].Name);

                    // create our custom icon that can be shown in 2D or 3D modes
                    VECustomIconSpecification cis = new VECustomIconSpecification();
                    cis.Image = findLayer.IconPath;
                    cis.CustomHtml = "<img src='" + findLayer.IconPath + "' />";
                    fp.CustomIconSpecification = cis;

                    // add shape to layer
                    layer.AddShape(fp);
                    // now add the layer to the map
                    map.AddShapeLayer(layer);
                }

                // example code to go through all the VEPlace data
                foreach (VEPlace pl in e.Places)
                {
                    string foundPlace = pl.LatLong.ToString() + "|" + pl.MatchCode + "|" + pl.MatchConfidence;
                    foreach (VEGeocodeLocation gl in pl.Locations)
                    {
                        foundPlace += gl.LatLong.ToString();
                    }
                }
            }
        }

        private void map_RouteCompleted(object sender, RouteCompletedEventArgs e)
        {
            double distAll = e.Route.Distance;
            double timeAll = e.Route.Time;
            foreach (VERouteLeg leg in e.Route.RouteLegs)
            {
                double distLeg = leg.Distance;
                double timeLeg = leg.Time;
                VERouteItinerary iten = leg.Itinerary;
                foreach (VERouteItineraryItem item in iten.Items)
                {
                    // add VERouteItineraryItem to the listbox, it uses databinding to get the elements it needs
                    paneInfo.lstRoute.Add(item);

                    // example code of how these items might be used...
                    StringBuilder sb = new StringBuilder();
                    double dist = item.Distance;
                    double time = item.Time;
                    TimeSpan ts = new TimeSpan(0, 0, (int)time);
                    string stop = item.Text;
                    sb.AppendFormat("{0} - {1:###.##}, {2}", stop, dist, ts.ToString());
                    VELatLong ll = item.LatLong;
                    VEShape shape = item.Shape;
                    if (shape != null)
                    {
                        sb.Append("-");
                        if (shape is VEPushpin)
                        {
                            sb.Append("PushPin");
                        }
                        else if (shape is VEPolygon)
                        {
                            sb.Append("Polygon");
                        }
                        else if (shape is VEPolyline)
                        {
                            sb.Append("Polyline");
                        }
                    }
                }
            }
        }
        #endregion // Overrides / Event Handlers

        #region Public Methods
        /// <summary>
        /// Find a business/location
        /// </summary>
        /// <param name="what">what you are looking for</param>
        /// <param name="where">where you are looking for it</param>
        public void Find(string what, string where)
        {
            FindOptions options = new FindOptions();
            // options.callback = true; - This is the default now.
            options.CreateResults = false;

            // create a new shapelayer to hold the shapes
            VEShapeLayer shapeLayer = new VEShapeLayer();
            // store all the relavant info in a structure
            FindLayer findLayer = new FindLayer(what, where, shapeLayer, GetNextIcon());
            string str = findLayer.GetFindString();
            shapeLayer.Title = str;
            // we add this early, before we get the results, might want to change...
            paneInfo.lstFind.Add(findLayer);

            // now do the find
            map.Find(what, where, options);
        }

        /// <summary>
        /// Get directions from one location to another
        /// </summary>
        /// <param name="from">start of the path</param>
        /// <param name="to">end of the path</param>
        public void GetDirections(string from, string to)
        {
            VERouteOptions routeOptions = new VERouteOptions();
            routeOptions.DistanceUnit = VERouteDistanceUnit.Mile;
            routeOptions.DrawRoute = true;
            routeOptions.RouteOptimize = VERouteOptimize.MinimizeTime;
            routeOptions.RouteCallback = HtmlPage.Window.GetProperty("RouteResults");
            ; // NOTE not real method, it will get replaced by a Javascript method under the covers
            routeOptions.RouteColor = new VEColor(GetNextColor(0.75));
            routeOptions.RouteWeight = 5;
            routeOptions.RouteZIndex = 10;
            routeOptions.SetBestMapView = true;
            routeOptions.ShowDisambiguation = true;
            routeOptions.ShowErrorMessages = true;
            routeOptions.UseMapPointWebService = false;
            string[] arrStr = new string[2];
            arrStr[0] = from;
            arrStr[1] = to;
            paneInfo.lstRoute.Clear();
            map.GetDirections(arrStr, routeOptions);
        }

        /// <summary>
        /// Initializes the controller, setting up variables, events, and loading the map.
        /// </summary>
        /// <param name="containerId">
        /// The JavaScript ID of the container where the map will be placed.
        /// </param>
        /// <remarks>
        /// This is the true entrypoint to this sample application. It is called when the 
        /// onPluginLoaded event is handled in the HTML page.
        /// </remarks>
        public void Initialize(string containerId)
        {
            // Create the map in the specified container
            map = new VEMap(containerId);

            // Register the map wrapper as scriptable
            HtmlPage.RegisterScriptableObject("SLMAP", map);

            // Navigate to a default location
            // VELatLong ll = new VELatLong();
            // map.LoadMap(ll, 10, VEMapStyle.Road, false, VEMapMode.Mode2D, true, 2);

            // Load the default map
            map.LoadMap();

            // Subscribe to events
            map.Click += new EventHandler<VEMapEventArgs>(map_Click);
            map.Error += new EventHandler<VEMapEventArgs>(map_Error);
            map.FindCompleted += new EventHandler<FindCompletedEventArgs>(map_FindCompleted);
            map.RouteCompleted += new EventHandler<RouteCompletedEventArgs>(map_RouteCompleted);
        }
        #endregion // Public Methods

        #region Public Properties
        /// <summary>
        /// Property to retrieve the Map object
        /// </summary>
        public VEMap Map
        {
            get { return map; }
        }
        #endregion // Public Properties
    }
}
